<?php
namespace Think;
error_reporting(E_ALL);

class ErrorLog {

    private static $lastError = array('error' => null);                                                                 //最后一次错误

    /**
     * 描述 : 获取最后一次错误
     * 作者 : Edgar.lee
     */
    public static function lastError($clean = false) {
        $clean && self::$lastError['error'] = null;
        return self::$lastError['error'];
    }

    /**
     * 描述 : 记录php错误及异常
     * 作者 : Edgar.lee
     */
    public static function phplog($errno, $errstr = null, $errfile = null, $errline= null) {
        if($errstr === null) {                                                                                          //异常
            $backtrace = array(
                'logType'            => 'exception',
                'environment'        => array(
                    'code'           => $errno->getCode(),                                                              //异常代码
                    'message'        => $errno->getMessage(),                                                           //异常消息
                    'file'           => $errno->getFile(),                                                              //异常文件
                    'line'           => $errno->getLine(),                                                              //异常行
                    'debugBacktrace' => $errno->getTrace()                                                              //异常追踪
                )
            );
        } else if( error_reporting() ) {                                                                                //常规错误
            $backtrace = array(
                'logType'            =>'error',
                'environment'        => array(
                    'code'           => $errno,
                    'message'        => $errstr,
                    'file'           => $errfile,
                    'line'           => $errline,
                    'debugBacktrace' => debug_backtrace()                                                               //错误回溯
                )
            );
        } else {                                                                                                        //"@"错误
            return ;
        }

        $index = iconv('UTF-8', 'UTF-8//IGNORE', $index = &$backtrace['environment']['message']);                       //移除无效字符
        self::formatLog($backtrace);				                                                                    //格式化日志

        $errorLevel = array(                                                                                            //输出日志信息
            0     => 'Exception',                   //异常
            1     => 'E_ERROR',                     //致命的运行时错误。错误无法恢复。脚本的执行被中断。
            2     => 'E_WARNING',                   //非致命的运行时错误。脚本的执行不会中断。
            4     => 'E_PARSE',                     //编译时语法解析错误。解析错误只应该由解析器生成。
            8     => 'E_NOTICE',                    //运行时提示。可能是错误，也可能在正常运行脚本时发生。
            16    => 'E_CORE_ERROR',                //由 PHP 内部生成的错误。
            32    => 'E_CORE_WARNING',              //由 PHP 内部生成的警告。
            64    => 'E_COMPILE_ERROR',             //由 Zend 脚本引擎内部生成的错误。
            128   => 'E_COMPILE_WARNING',           //由 Zend 脚本引擎内部生成的警告。
            256   => 'E_USER_ERROR',                //由于调用 trigger_error() 函数生成的运行时错误。
            512   => 'E_USER_WARNING',              //由于调用 trigger_error() 函数生成的运行时警告。
            1024  => 'E_USER_NOTICE',               //由于调用 trigger_error() 函数生成的运行时提示。
            2048  => 'E_STRICT',                    //运行时提示。对增强代码的互用性和兼容性有益。
            4096  => 'E_RECOVERABLE_ERROR',         //可捕获的致命错误。
            8192  => 'E_DEPRECATED',                //运行时通知。启用后将会对在未来版本中可能无法正常工作的代码给出警告。
            16384 => 'E_USER_DEPRECATED',           //用户产少的警告信息。
            30719 => 'E_ALL',                       //所有的错误和警告，除了 E_STRICT。
        );

        $temp = htmlentities($index, ENT_QUOTES, 'UTF-8');
        $errorLevelStr 	= isset($errorLevel[$backtrace['environment']['code']])?$errorLevel[$backtrace['environment']['code']]:'';
	self::writeLog($backtrace, 'php', "<font style='display:block; color:#F00; font-weight:bold;'>{$errorLevelStr} : \"<pre style='display: inline;'>{$temp}</pre>\" in {$backtrace['environment']['file']} on line {$backtrace['environment']['line']}. Timestamp : {$_SERVER['REQUEST_TIME']}</font>");
        //self::writeLog($backtrace, 'php', "<font style='display:block; color:#F00; font-weight:bold;'>{$errorLevel[$backtrace['environment']['code']]} : \"<pre style='display: inline;'>{$temp}</pre>\" in {$backtrace['environment']['file']} on line {$backtrace['environment']['line']}. Timestamp : {$_SERVER['REQUEST_TIME']}</font>");
    }


	/**
	 * 描述 : 记录sql执行超过1秒
	 * 参数 :
	 *     params : sql的错误
	 * 作者 : Edgar.lee
	 */
	public static function sql1log($params) {

		$sysBacktrace = debug_backtrace();

		$backtrace = array(                                                                                             //生成错误列表
			'logType'            => 'sqlError',
			'environment'        => array(
				'code'           => $params['code'],
				'message'        => &$params['sql'],
				'file'           => '(',
				'line'           => 0,
				'debugBacktrace' => &$sysBacktrace
			)
		);

		self::formatLog($backtrace);                                                                     				//格式化日志

		$temp = htmlentities($backtrace['environment']['message'], ENT_QUOTES, 'UTF-8');                                //输出错误日志
		self::writelog($backtrace, 'sql1', "<font style='display:block; color:#F00; font-weight:bold;'>[{$backtrace['environment']['code']}] : \"<pre style='display: inline;'>{$temp}</pre>\" in {$backtrace['environment']['file']} on line {$backtrace['environment']['line']}. Timestamp : {$_SERVER['REQUEST_TIME']}</font>");
	}

    /**
     * 描述 : 记录sql错误
     * 参数 :
     *     params : sql的错误
     * 作者 : Edgar.lee
     */
    public static function sqllog($params) {
        $sysBacktrace = debug_backtrace();
        $backtrace = array(                                                                                             //生成错误列表
            'logType'            => 'sqlError',
            'environment'        => array(
                'code'           => $params['code'],
                'message'        => &$params['sql'],
                'file'           => '(',
                'line'           => 0,
                'debugBacktrace' => &$sysBacktrace
            )
        );
        self::formatLog($backtrace);                                                                     				//格式化日志
        $temp = htmlentities($backtrace['environment']['message'], ENT_QUOTES, 'UTF-8');                                //输出错误日志
        self::writelog($backtrace, 'sql', "<font style='display:block; color:#F00; font-weight:bold;'>[{$backtrace['environment']['code']}] : \"<pre style='display: inline;'>{$temp}</pre>\" in {$backtrace['environment']['file']} on line {$backtrace['environment']['line']}. Timestamp : {$_SERVER['REQUEST_TIME']}</font>");
    }

    /**
     * 描述 : 记录日志数据
     * 参数 :
     *     &logData  : 日志数据
     *      logType  : 日志内容[js, php, mysql]
     *      printStr : 显示错误内容,会根据相关配置绝对是否显示
     * 作者 : Edgar.lee
     */
    protected static function writelog(&$logData, $logType, $printStr) {
        self::$lastError['error'] = &$logData;
        $logPath = RUNTIME_PATH . 'Errorlog' . date('/Y/m/dH', $_SERVER['REQUEST_TIME']) . $logType;
        is_dir($temp = dirname($logPath)) || mkdir($temp, 0777, true);
        if (!file_exists($logPath)) {
            touch($logPath);
            chmod($logPath,0777);
        }
        @file_put_contents(
            $logPath,
            strtr(serialize($logData), array("\r\n" => ' ' . ($temp = chr(0)), "\r" => $temp, "\n" => $temp)) . "\n",
            FILE_APPEND | LOCK_EX
        );
    }

    /**
     * 描述 : 格式化日志
     * 参数 :
     *     &logData : 日志数据
     * 结构 : {
     *      'logType'     : 错误类型(sqlError, exception, error)
     *      'environment' : {    错误体,包括环境,错误细节,回溯
     *          'code'=>              : php=错误级别, sql=错误码及说明
     *          'message'             : php=错误描述, sql=错误sql
     *          'file'                : 定位->路径
     *          'line'                : 定位->行数
     *          'predefinedVariables' : {    环境变量
     *              '_ENV'     : 对应超全局变量
     *              '_POST'    : 对应超全局变量
     *              '_GET'     : 对应超全局变量
     *              '_COOKIE'  : 对应超全局变量
     *              '_SERVER'  : 对应超全局变量
     *              '_REQUEST' : 对应超全局变量
     *              'config'   : 配置文件
     *          }
     *          'debugBacktrace'     : {}    //回溯信息,js没有
     *      }
     *      'time'        : 生成时间戳
     *  }
     * 作者 : Edgar.lee
     */
    private static function formatLog( &$logData ) {
        $debugBacktrace = &$logData['environment']['debugBacktrace'];                                                   //引用回溯
        if( strpos($logData['environment']['file'], '(') !== false ) {                                                  //debug运行追踪
            foreach($debugBacktrace as $k => &$v) {
                if( isset($v['file']) ) {
                    $temp = array(strtr($v['file'], '\\', '/'), false);
                    if(($temp[1] = strpos($temp[0], '(')) !== false || ($logData['logType'] === 'sqlError')) {
                        if( is_int($temp[1]) ) {                                                                        //eval中的错误
                            $temp[0] = &$debugBacktrace[$k + 1];
                            if( isset($temp[0]['file']) ) {                                                             //eval中的过程错误
                                $logData['environment']['file'] = substr($temp[0]['file'], 0, $temp[1]);
                                $logData['environment']['line'] = substr($temp[0]['file'], $temp[1] + 1, strpos($temp[0]['file'], ')') - $temp[1] - 1);
                                $logData['environment']['line'] || $logData['environment']['line'] = $temp[0]['line'];  //eval 中的异常
                            } else {                                                                                    //eval中的对象错误 isset($temp[0]['class'])
                                $logData['environment']['file'] = $v['file'];
                                $logData['environment']['line'] = $debugBacktrace[$k]['line'];
                            }
                        } else {                                                                                        //正常执行的错误
                            $logData['environment']['file'] = $v['file'];
                            $logData['environment']['line'] = $v['line'];
                        }
                        array_splice($debugBacktrace, 0, $k);
                        break;
                    }
                }
            }
        } elseif( $logData['logType'] === 'error' ) {
            array_splice($debugBacktrace, 0, 1);
        }

        $logData['environment']['predefinedVariables'] = array(                                                         //添加预定义数据
            //'_ENV'      => &$_ENV,                                                                                    //服务器变量
            '_POST'    => &$_POST,
            '_GET'     => &$_GET,
            '_COOKIE'  => &$_COOKIE,
            '_SESSION' => &$_SESSION,
            '_SERVER'  => &$_SERVER,
            '_REQUEST' => &$_REQUEST
        );
        //$logData['environment']['predefinedVariables']['config'] = of::config();                                      //存储配置文件
        $logData['time']   = $_SERVER['REQUEST_TIME'];

        if( isset($logData['environment']['debugBacktrace']) ) {                                                        //格式化回溯
            foreach($logData['environment']['debugBacktrace'] as &$v) {
                if(isset($v['object'])) {
                    unset($v['object']);
                }
                if(isset($v['args'])) {
                    $temp = array();                                                                                    //临时参数拷贝数组
                    foreach($v['args'] as &$arg) {
                        if(is_scalar($arg) || is_resource($arg) || $arg === null) {                                     //是一个标量,资源,null
                            $temp[] = gettype($arg) . ' (' . var_export($arg, true) . ')';
                        } else if(is_object($arg)) {                                                                    //对象
                            $temp[] = 'object (' . get_class($arg) . ')';
                        } else if(is_array($arg)) {                                                                     //数组
                            $temp[] = var_export($arg, true);
                        }
                    }
                    $v['args'] = $temp;
                }
            }
        }
    }
}